package com.ltwc.lttv

import android.os.Handler
import android.os.Looper
import android.util.Base64
import com.ltwc.lttv.api.ApiClient
import com.ltwc.lttv.api.Auth
import com.ltwc.lttv.api.AuthRequest
import com.ltwc.lttv.api.DataInfo
import com.ltwc.lttv.api.FAuth
import com.ltwc.lttv.api.Info
import com.ltwc.lttv.api.KvcollectRequest2
import com.ltwc.lttv.api.LiveInfo
import com.ltwc.lttv.api.LiveInfoRequest
import com.ltwc.lttv.api.MyApiClient
import com.ltwc.lttv.api.TypeInfo
import com.ltwc.lttv.api.YSP
import com.ltwc.lttv.api.YSPApiService
import com.ltwc.lttv.api.YSPBtraceService
import com.ltwc.lttv.api.YSPInfoService
import com.ltwc.lttv.api.YSPProtoService
import com.ltwc.lttv.listener.InfoDataCallBack
import com.ltwc.lttv.listener.InfoTypeCallBack
import com.ltwc.lttv.models.TVViewModel
import com.ltwc.lttv.net.LogUtil
import com.ltwc.lttv.proto.Ysp
import com.ltwc.lttv.tools.Utils.getDateFormat
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import kotlin.random.Random

object Request {
    private var yspApiService: YSPApiService = ApiClient().yspApiService
    private var yspBtraceService3: YSPBtraceService = ApiClient().yspBtraceService3
    private var yspProtoService: YSPProtoService = ApiClient().yspProtoService
    private var yspInfoService: YSPInfoService = MyApiClient().yspInfoService

    private var openid = ""
    private var token = ""
    private var tokenFH = ""

    private var needAuth = true
    private var needToken = false

    private val handler = Handler(Looper.getMainLooper())
    private lateinit var btraceRunnable: BtraceRunnable

    private val regex = Regex("""des_key = "([^"]+).+var des_iv = "([^"]+)""")
    private val input =
        """{"mver":"1","subver":"1.2","host":"www.yangshipin.cn/#/tv/home?pid=","referer":"","canvas":"YSPANGLE(Apple,ANGLEMetalRenderer:AppleM1Pro,UnspecifiedVersion)"}""".toByteArray()

    fun onDestroy() {
        LogUtil.i("onDestroy")
    }

    var callback: InfoDataCallBack? = null
    var typeInfoCall: InfoTypeCallBack? = null
    private var call: Call<LiveInfo>? = null
    private var callAuth: Call<Auth>? = null
    private var callInfo: Call<Info>? = null
    private var callFAuth: Call<FAuth>? = null
    private var callPage: Call<Ysp.cn.yangshipin.oms.common.proto.pageModel.Response>? = null

    private var callBtracePage: Call<Void>? = null

    private fun cancelCall() {
        call?.cancel()
        callAuth?.cancel()
        callInfo?.cancel()
        callFAuth?.cancel()
        callPage?.cancel()
    }

    private fun fetchAuth(tvModel: TVViewModel, cookie: String) {
        cancelCall()

        val title = tvModel.getTV().title

        val data = YSP.getAuthData(tvModel)
        val request = AuthRequest(data)
        callAuth = request.let { yspApiService.getAuth("guid=${YSP.getGuid()}; $cookie", it) }
        callAuth?.enqueue(object : Callback<Auth> {
            override fun onResponse(call: Call<Auth>, response: Response<Auth>) {
                LogUtil.e("response--->" + response.message())
                if (response.isSuccessful) {
                    val liveInfo = response.body()

                    if (liveInfo?.data?.token != null) {
                        LogUtil.i("token ${liveInfo.data.token}")
                        YSP.token = liveInfo.data.token
                        fetchVideo(tvModel, cookie)
                    } else {
                        LogUtil.e("$title token error")
                        if (tvModel.retryTimes < tvModel.retryMaxTimes) {
                            tvModel.retryTimes++
                            if (tvModel.getTV().needToken) {
                                if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
                                    tvModel.tokenYSPRetryTimes++
                                    //tvModel.needGetToken = true
                                    fetchAuth(tvModel)
                                } else {
                                    if (!tvModel.getTV().mustToken) {
                                        fetchAuth(tvModel, cookie)
                                    }
                                }
                            } else {
                                fetchAuth(tvModel, cookie)
                            }
                        }
                    }
                } else {
                    LogUtil.e("$title auth status error")
                    if (tvModel.retryTimes < tvModel.retryMaxTimes) {
                        tvModel.retryTimes++
                        if (tvModel.getTV().needToken) {
                            if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
                                tvModel.tokenYSPRetryTimes++
                                tvModel.needGetToken = true
                                fetchAuth(tvModel)
                            } else {
                                if (!tvModel.getTV().mustToken) {
                                    fetchAuth(tvModel, cookie)
                                }
                            }
                        } else {
                            fetchAuth(tvModel, cookie)
                        }
                    }
                }
            }

            override fun onFailure(call: Call<Auth>, t: Throwable) {
                LogUtil.e("$title auth request error $t")
                if (tvModel.retryTimes < tvModel.retryMaxTimes) {
                    tvModel.retryTimes++
                    if (tvModel.getTV().needToken) {
                        if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
                            tvModel.tokenYSPRetryTimes++
                            tvModel.needGetToken = true
                            fetchAuth(tvModel)
                        } else {
                            if (!tvModel.getTV().mustToken) {
                                fetchAuth(tvModel, cookie)
                            }
                        }
                    } else {
                        fetchAuth(tvModel, cookie)
                    }
                }
            }
        })
    }

    private fun fetchVideo(tvModel: TVViewModel, cookie: String) {
        cancelCall()

        if (::btraceRunnable.isInitialized) {
            handler.removeCallbacks(btraceRunnable)
        }

        val title = tvModel.getTV().title

        tvModel.seq = 0
        val data = YSP.switch(tvModel)
        val request = LiveInfoRequest(data)
        call = request.let {
            yspApiService.getLiveInfo(
                "guid=${YSP.getGuid()}; $cookie", YSP.token, it
            )
        }

        call?.enqueue(object : Callback<LiveInfo> {
            override fun onResponse(call: Call<LiveInfo>, response: Response<LiveInfo>) {
                if (response.isSuccessful) {
                    val liveInfo = response.body()

                    if (liveInfo?.data?.playurl != null) {
                        val chanll = liveInfo.data.chanll
                        val decodedBytes = Base64.decode(
                            chanll.substring(9, chanll.length - 3), Base64.DEFAULT
                        )
                        val decodedString = String(decodedBytes)
                        val matchResult = regex.find(decodedString)
                        if (matchResult != null) {
                            val (key, iv) = matchResult.destructured
                            val keyBytes = Base64.decode(key, Base64.DEFAULT)
                            val ivBytes = Base64.decode(iv, Base64.DEFAULT)
                            var url = liveInfo.data.playurl + "&revoi=" + encryptTripleDES(
                                keyBytes + byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0), ivBytes
                            ).uppercase() + liveInfo.data.extended_param
                            LogUtil.d("$title url $url")
                            tvModel.addVideoUrl(url)
                            tvModel.allReady()
                            tvModel.retryTimes = 0
                            btraceRunnable = BtraceRunnable(tvModel)
                            handler.post(btraceRunnable)
                        } else {
                            LogUtil.e("$title key error")
                            if (tvModel.retryTimes < tvModel.retryMaxTimes) {
                                tvModel.retryTimes++
                                if (tvModel.getTV().needToken) {
                                    if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
                                        tvModel.tokenYSPRetryTimes++
                                        tvModel.needGetToken = true
                                        fetchVideo(tvModel)
                                    } else {
                                        if (!tvModel.getTV().mustToken) {
                                            fetchVideo(tvModel, cookie)
                                        }
                                    }
                                } else {
                                    fetchVideo(tvModel, cookie)
                                }
                            }
                        }
                    } else {
                        if (liveInfo?.data?.errinfo != null && liveInfo.data.errinfo == "应版权方要求，暂停提供直播信号，请点击观看其他精彩节目") {
                            LogUtil.e("$title error ${liveInfo.data.errinfo}")
                            tvModel.setErrInfo(liveInfo.data.errinfo)
                        } else {
                            LogUtil.e("$title url error $request $liveInfo")
                            if (tvModel.retryTimes < tvModel.retryMaxTimes) {
                                tvModel.retryTimes++
                                if (tvModel.getTV().needToken) {
                                    if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
                                        tvModel.tokenYSPRetryTimes++
                                        tvModel.needGetToken = true
                                        fetchVideo(tvModel)
                                    } else {
                                        if (!tvModel.getTV().mustToken) {
                                            fetchVideo(tvModel, cookie)
                                        }
                                    }
                                } else {
                                    fetchVideo(tvModel, cookie)
                                }
                            }
                        }
                    }
                } else {
                    LogUtil.e("$title status error $data")
                    if (tvModel.retryTimes < tvModel.retryMaxTimes) {
                        tvModel.retryTimes++
                        if (tvModel.getTV().needToken) {
                            if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
                                tvModel.tokenYSPRetryTimes++
                                tvModel.needGetToken = true
                                fetchVideo(tvModel)
                            } else {
                                if (!tvModel.getTV().mustToken) {
                                    fetchVideo(tvModel, cookie)
                                }
                            }
                        } else {
                            fetchVideo(tvModel, cookie)
                        }
                    }
                }
            }

            override fun onFailure(call: Call<LiveInfo>, t: Throwable) {
                LogUtil.e("$title fetchVideo request error $t")
                if (tvModel.retryTimes < tvModel.retryMaxTimes) {
                    tvModel.retryTimes++
                    if (tvModel.getTV().needToken) {
                        if (needToken && tvModel.tokenYSPRetryTimes < tvModel.tokenYSPRetryMaxTimes) {
                            tvModel.tokenYSPRetryTimes++
                            tvModel.needGetToken = true
                            fetchVideo(tvModel)
                        } else {
                            if (!tvModel.getTV().mustToken) {
                                fetchVideo(tvModel, cookie)
                            }
                        }
                    } else {
                        fetchVideo(tvModel, cookie)
                    }
                }
            }
        })
    }

    private fun fetchAuth(tvModel: TVViewModel) {
        cancelCall()
        val cookie =
            "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109;yspopenid=$openid; vusession=$token"
        fetchAuth(tvModel, cookie)
    }

    private fun fetchVideo(tvModel: TVViewModel) {
        cancelCall()
        LogUtil.d("fetchVideo")
        val cookie =
            "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; deviceModel=120; appid=1400421205; yspappid=519748109;"
        fetchVideo(tvModel, cookie)
    }


    fun fetchData(tvModel: TVViewModel) {
        val cookie =
            "versionName=99.99.99; versionCode=999999; vplatform=109; platformVersion=Chrome; updateProtocol=1; deviceModel=120"
        fetchAuth(tvModel, cookie)

    }

    class BtraceRunnable(private val tvModel: TVViewModel) : Runnable {
        override fun run() {
            fetchBtrace3(tvModel)
        }
    }

    fun fetchBtrace3(tvModel: TVViewModel) {
        callBtracePage?.cancel()
        //TODO:这里让重新请求一次播放链接，貌似央视频链接5分钟左右的时候就会让链接失效
        if (tvModel.seq > 3) {
            tvModel.changed()
            return
        }
        val title = tvModel.getTV().title

        val guid = YSP.getGuid()
        val pid = tvModel.getTV().pid
        val sid = tvModel.getTV().sid
        val randomNumber = Random.nextDouble()
        val url = tvModel.getVideoUrlCurrent()

        val r = KvcollectRequest2(
            guid = guid,
            prog = sid,
            viewid = sid,
            livepid = pid,
            sUrl = "https://www.yangshipin.cn/#/tv/home?pid=$pid",
            playno = YSP.getRand(),
            rand_str = YSP.getRand(),
            ftime = getDateFormat("yyyy-MM-dd HH:mm:ss"),
            seq = tvModel.seq,
            durl = url,
            url = url,
            _dc = randomNumber,
        )

        val e =
            "BossId=${r.BossId}&Pwd=${r.Pwd}&_dc=${r._dc}&cdn=${r.cdn}&cmd=${r.cmd}&defn=${r.defn}&downspeed=${r.downspeed}&durl=${r.durl}&errcode=${r.errcode}&fact1=${r.fact1}&firstreport=${r.firstreport}&fplayerver=${r.fplayerver}&ftime=${r.ftime}&geturltime=${r.geturltime}&guid=${r.guid}&hc_openid=${r.hc_openid}&hh_ua=${r.hh_ua}&live_type=${r.live_type}&livepid=${r.livepid}&login_type=${r.login_type}&open_id=${r.open_id}&openid=${r.openid}&platform=${r.platform}&playno=${r.playno}&prd=${r.prd}&prog=${r.prog}&rand_str=${r.rand_str}&sRef=${r.sRef}&sUrl=${r.sUrl}&sdtfrom=${r.sdtfrom}&seq=${r.seq}&url=${r.url}&viewid=${r.viewid}"
        r.signature = YSP.getAuthSignature(e)

        callBtracePage = yspBtraceService3.kvcollect3(
            guid = r.guid,
            prog = r.prog,
            viewid = r.viewid,
            livepid = r.livepid,
            sUrl = r.sUrl,
            playno = r.playno,
            rand_str = r.rand_str,
            ftime = r.ftime,
            seq = "${r.seq}",
            durl = r.durl,
            url = r.url,
            _dc = "${r._dc}",
            signature = r.signature
        )
        callBtracePage?.enqueue(object : Callback<Void> {
            override fun onResponse(call: Call<Void>, response: Response<Void>) {
                if (response.isSuccessful) {
                    LogUtil.d("$title kvcollect success")
                } else {
                    LogUtil.e("$title kvcollect status error")
                }
                handler.postDelayed(btraceRunnable, 60 * 1000)
            }

            override fun onFailure(call: Call<Void>, t: Throwable) {
                LogUtil.e("$title kvcollect request error")
                handler.postDelayed(btraceRunnable, 60 * 1000)
            }
        })
        tvModel.seq++
    }

    private fun encryptTripleDES(key: ByteArray, iv: ByteArray): String {
        return try {
            val keySpec = SecretKeySpec(key, "DESede")
            val ivSpec = IvParameterSpec(iv)
            val cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding")
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec)
            return cipher.doFinal(input).let { it -> it.joinToString("") { "%02x".format(it) } }
        } catch (e: Exception) {
            e.printStackTrace()
            ""
        }
    }

    fun fetchProgram(tvViewModel: TVViewModel) {
        val title = tvViewModel.title.value
        yspProtoService.getProgram(tvViewModel.programId.value!!, getDateFormat("yyyyMMdd"))
            .enqueue(object :
                Callback<Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel.Response> {
                override fun onResponse(
                    call: Call<Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel.Response>,
                    response: Response<Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel.Response>
                ) {
                    if (response.isSuccessful) {
                        val program = response.body()
                        if (program != null) {
                            tvViewModel.addProgram(program.dataListList)
                            LogUtil.i("$title program ${program.dataListList.size}")
                        }
                    }
                }

                override fun onFailure(
                    call: Call<Ysp.cn.yangshipin.omstv.common.proto.epgProgramModel.Response>,
                    t: Throwable
                ) {
                    LogUtil.e("$title program request failed $t")
                }
            })
    }

    fun initInfoDataCallBack(mCallback: InfoDataCallBack) {
        callback = mCallback
    }

    fun fetchDataInfo() {
        yspInfoService.getDataInfo().enqueue(object : Callback<DataInfo> {
            override fun onResponse(call: Call<DataInfo>, response: Response<DataInfo>) {
                if (response.isSuccessful) {
                    if (response.code() == 200) {
                        val dataInfo = response.body() as DataInfo
                        if (dataInfo.code == 401 || dataInfo.code == 500) {
                            dataInfo.msg?.let { callback?.callError(it) }
                        } else if (dataInfo.code == 200) {
                            val info = (response.body() as DataInfo).rows
                            callback?.call(info)
                        } else {
                            callback?.callError(response.message())
                        }

                    } else {
                        callback?.callError(response.message())
                    }

                }
            }

            override fun onFailure(call: Call<DataInfo>, t: Throwable) {
                LogUtil.e("----->" + t.message)
                t.message?.let { callback?.callError(it) }
            }

        })
    }

    fun initInfoTypeCallBack(mCallback: InfoTypeCallBack) {
        typeInfoCall = mCallback
    }

    fun getTypeList() {
        yspInfoService.getTypeList().enqueue(object : Callback<TypeInfo> {
            override fun onResponse(call: Call<TypeInfo>, response: Response<TypeInfo>) {
                if (response.isSuccessful) {
                    if (response.code() == 200) {
                        val info = (response.body() as TypeInfo).rows
                        typeInfoCall?.call(info)
                    } else {
                        typeInfoCall?.callError(response.message())
                    }
                }
            }

            override fun onFailure(call: Call<TypeInfo>, t: Throwable) {
                t.message?.let { typeInfoCall?.callError(it) }
            }

        })
    }
}